package com.zslibrary.Protocol;

import android.os.SystemClock;
import android.util.Log;

import com.zslibrary.Enum.BoardId;
import com.zslibrary.Enum.CommunicationError;
import com.zslibrary.Enum.PipeResultDefine;
import com.zslibrary.Enum.Protocol;
import com.zslibrary.Enum.StateEventDefine;
import com.zslibrary.Pipe.Pipe;
import com.zslibrary.Structure.GeneralRequestStruct;
import com.zslibrary.Structure.HardwareVersionStruct;
import com.zslibrary.Structure.MessageStruct;
import com.zslibrary.Structure.ReplyStruct;
import com.zslibrary.Structure.SoftwareVersionStruct;
import com.zslibrary.Tools.FrameBuilder;
import com.zslibrary.Tools.ZSLog;

import java.io.File;
import java.io.FileInputStream;
import java.util.Arrays;

public class Version {
    private HardwareVersionStruct[] mHardwareVersions=null;
    private SoftwareVersionStruct[] mSoftwareVersions=null;
    private int mUpdateBoard;
    private int mPackageNumber;
    private int mUpdateType;
    private String mSoftwarefile;
    private File mProgramFile;
    private int mUpdateResult;
    private int mProgress=0;
    private Boolean isNeedRetry=false;
    private Boolean isPrepareDone=true;
    private Boolean isPackageOk=true;

    public static final int UPDATE_DONE=0;
    public static final int READY_FAILED=-1;
    public static final int UPDATE_FAILED=-2;
    public static final int NO_FILE_FOUND=-3;
    public static final int WRONG_FILE_SIZE=-4;
    private static final int PACKAGE_LENGTH=1024;

    public @StateEventDefine.StateEvent int getBoardHardwareVersion(Pipe pipe){
        mProgress=0;
        GeneralRequestStruct request=new GeneralRequestStruct();
        request.requstId= Protocol.MSG_GET_HARDWARE_VERSIONS;
        request.dataLength=0;
        request.data=null;
        mHardwareVersions=null;

        Pipe.HandleMessageCallback callback=new Pipe.HandleMessageCallback() {
            @Override
            public Boolean handle(MessageStruct[] messages) {
                for (MessageStruct message : messages) {
                    FrameBuilder builder=new FrameBuilder(message.messageData);
                    int versionSize=builder.popData(2);
                    HardwareVersionStruct[] versions=new HardwareVersionStruct[versionSize];
                    for(int i=0;i<versionSize;i++){
                        HardwareVersionStruct version=new HardwareVersionStruct();
                        version.boardAdress=builder.popData(4);
                        int strLen=builder.popData(2);
                        byte[] versionBytes=builder.popBytes(strLen);
                        version.version=new String(versionBytes);
                        versions[i]=version;
                    }
                    mHardwareVersions=versions;
                }
                return true;
            }
        };

        int result=pipe.synchronousPipe(request,callback);
        if(result==PipeResultDefine.SYNC_BUSY)
            return StateEventDefine.EVT_EXECUTE;    /*busy*/
        if(result==PipeResultDefine.SYNC_ERROR)
            return StateEventDefine.EVT_DISCONNECT;     /*disconnect*/
        return StateEventDefine.EVT_NONE;
    }

    public HardwareVersionStruct[] getBoardHardwareVersion(){
        return mHardwareVersions;
    }

    public @StateEventDefine.StateEvent int getBoardSoftwareVersions(Pipe pipe){
        mProgress=0;
        GeneralRequestStruct request=new GeneralRequestStruct();
        request.requstId=Protocol.MSG_GET_SOFTWARE_VERSIONS;
        request.dataLength=0;
        request.data=null;
        mSoftwareVersions=null;
        Pipe.HandleMessageCallback handler=new Pipe.HandleMessageCallback() {
            @Override
            public Boolean handle(MessageStruct[] messages) {
                for (MessageStruct message : messages) {
                    FrameBuilder builder = new FrameBuilder(message.messageData);
                    int versionSize=builder.popData(2);
                    SoftwareVersionStruct[] versions=new SoftwareVersionStruct[versionSize];
                    for(int i=0;i<versionSize;i++){
                        SoftwareVersionStruct version=new SoftwareVersionStruct();
                        version.boradAddress=builder.popData(4);
                        version.type=builder.popData(4);
                        int strLen=builder.popData(2);
                        byte[] versionBytes=builder.popBytes(strLen);
                        version.version=new String(versionBytes);
                        versions[i]=version;
                    }
                    mSoftwareVersions=versions;
                }
                return true;
            }
        };
        int result=pipe.synchronousPipe(request,handler);
        if(result== PipeResultDefine.SYNC_ERROR)
            return StateEventDefine.EVT_DISCONNECT;
        if(result==PipeResultDefine.SYNC_BUSY)
            return StateEventDefine.EVT_EXECUTE;    /*busy*/
        return StateEventDefine.EVT_NONE;
}

    public SoftwareVersionStruct[] getBoardSoftwareVersions(){
        return mSoftwareVersions;
    }

    public @StateEventDefine.StateEvent int updateSoftWare(Pipe pipe, int boardAddress,int type,String path){
        mProgress=0;
        mUpdateBoard=boardAddress;
        mUpdateType=type;
        isPrepareDone=true;
        isNeedRetry=false;
        isPackageOk=true;

        mUpdateResult=checkProgramFile(path);
        if(mUpdateResult!=UPDATE_DONE)
            return StateEventDefine.EVT_NONE;   //文件验证失败
        int result=prepareUpdate(pipe);
        if(result!=StateEventDefine.EVT_NONE||!isPrepareDone){  //准备失败
            mUpdateResult=READY_FAILED;
            return result;
        }
        SystemClock.sleep(1000);
        result= executeUpdate(pipe);
        SystemClock.sleep(2000);
        return result;
    }

    public int getUpdateResult(){
        return mUpdateResult;
    }

    public int getUpdateProgress(){
        //ZSLog.d("update progress"+mProgress);
        return mProgress;
    }

    private @StateEventDefine.StateEvent int executeUpdate(Pipe pipe){
        FileInputStream inputStream=null;
        mUpdateResult=UPDATE_FAILED;
        ZSLog.d("execute update");
        try{
            inputStream=new FileInputStream(mProgramFile);
            byte[] readBuff=new byte[1024];
            FrameBuilder fileBuilder=new FrameBuilder(mPackageNumber*PACKAGE_LENGTH);
            while((inputStream.read(readBuff))!=-1){
                fileBuilder.append(readBuff);
                Arrays.fill(readBuff,(byte)0xFF);
            }
            GeneralRequestStruct request=new GeneralRequestStruct();
            FrameBuilder dataBuilder=new FrameBuilder(PACKAGE_LENGTH+4+4);
            int packageSeq=1;
            Pipe.HandleMessageCallback callback=new Pipe.HandleMessageCallback() {
                @Override
                public Boolean handle(MessageStruct[] messages) {
                    for (MessageStruct message : messages) {
                        FrameBuilder builder = new FrameBuilder(message.messageData);
                        if (builder.popData(4) != 0) {  /*0:ok*/
                            ZSLog.d("update failed");
                            isPackageOk=false;  /*包失败*/
                        }
                    }
                    return true;
                }
            };

            for(int i=0;i<mPackageNumber;i++){
                dataBuilder.flushAll();
                dataBuilder.append(packageSeq);
                dataBuilder.append(fileBuilder.popBytes(PACKAGE_LENGTH));
                dataBuilder.append(PACKAGE_LENGTH);
                request.requstId=Protocol.MSG_UPDATE_PACKAGE;
                request.dataLength=PACKAGE_LENGTH+8;
                request.data=dataBuilder.toBytes();
                int result=pipe.synchronousPipe(request,callback);
                if(result==PipeResultDefine.SYNC_BUSY)
                    return StateEventDefine.EVT_EXECUTE;
                if(result==PipeResultDefine.SYNC_ERROR)
                    return StateEventDefine.EVT_RECONNECT;
                if(!isPackageOk)
                    return StateEventDefine.EVT_NONE;
                mProgress=packageSeq*100/mPackageNumber;
                packageSeq++;
                SystemClock.sleep(100);
            }
            ZSLog.d("update done");
            mUpdateResult=UPDATE_DONE;
        }catch (Exception e){
            ZSLog.d(e.toString());
        }finally {
            try {
                inputStream.close();
            } catch (Exception e) {
            }
        }
        return StateEventDefine.EVT_NONE;
    }

    private int checkProgramFile(String filePath){
        try{
            mProgramFile=new File(filePath);
            if(mProgramFile.exists()){
                String boardName=mProgramFile.getName().split("_")[0];
                if(((mUpdateBoard== BoardId.IO_BOARD)&&boardName.equals("IOBOARD"))||
                        ((mUpdateBoard==BoardId.CUP_BOARD)&&boardName.equals("CUPBOARD"))) {
                    String version = mProgramFile.getName().split("_V")[1];
                    if (!version.endsWith("bin")) {
                        ZSLog.d("no update file");
                        return WRONG_FILE_SIZE;
                    }
                    mSoftwarefile = version.split(".bin")[0];
                    int fileSize = (int) mProgramFile.length();
                    if ((fileSize < 1024) || (fileSize > 128 * 1024)) {
                        ZSLog.d("update file size wrong");
                        return WRONG_FILE_SIZE;
                    }
                    mPackageNumber = (int) Math.ceil(fileSize / (float) PACKAGE_LENGTH);
                    ZSLog.d("prepare update done");
                    return UPDATE_DONE;
                }
                ZSLog.d("no update file");
                return NO_FILE_FOUND;
            }
            ZSLog.d("no update file");
            return NO_FILE_FOUND;
        }catch (Exception e){
            ZSLog.d(e.toString());
        }
        ZSLog.d("prepare update failed");
        return READY_FAILED;
    }

    private @StateEventDefine.StateEvent int prepareUpdate(Pipe pipe){
        GeneralRequestStruct request=new GeneralRequestStruct();
        FrameBuilder dataBuilder=null;
        int tryTimes=0;
        dataBuilder = new FrameBuilder(mSoftwarefile.length() + 14);
        request.requstId = Protocol.MSG_START_UPDATE;
        request.dataLength = (short) (mSoftwarefile.length() + 14);
        dataBuilder.append(mUpdateBoard);
        dataBuilder.append(mUpdateType);
        dataBuilder.append((short) mSoftwarefile.length());
        dataBuilder.append(mSoftwarefile);
        dataBuilder.append(mPackageNumber * PACKAGE_LENGTH);
        request.data = dataBuilder.toBytes();
        Pipe.HandleMessageCallback callback = new Pipe.HandleMessageCallback() {
            @Override
            public Boolean handle(MessageStruct[] messages) {
                for (MessageStruct message : messages) {
                    FrameBuilder builder = new FrameBuilder(message.messageData);
                    int replyCode = builder.popData(4);
                    if (replyCode == 0) {
                        ZSLog.d("ready update");
                    } else if (replyCode == 1) { /*retry later*/
                        isNeedRetry=true;
                    } else
                        isPrepareDone=false;
                }
                return true;
            }
        };
        while (tryTimes++ < 2) {
            int result = pipe.synchronousPipe(request, callback);
            if (result == PipeResultDefine.SYNC_BUSY)
                return StateEventDefine.EVT_EXECUTE;    /*busy*/
            if (result == PipeResultDefine.SYNC_ERROR)
                return StateEventDefine.EVT_DISCONNECT;     /*disconnect*/
            if(!isNeedRetry) {
                break;
            }
            SystemClock.sleep(800); /*等待复位*/
        }
        return StateEventDefine.EVT_NONE;
    }
}
